package http

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"gitee.com/sdynasty/clever/errors"
	"gitee.com/sdynasty/clever/internal/endpoint"
	"gitee.com/sdynasty/clever/internal/host"
	"gitee.com/sdynasty/clever/log"
	"gitee.com/sdynasty/clever/transport/http/trace"
	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"golang.org/x/time/rate"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"runtime/debug"
	"strconv"
	"sync"
	"time"
)

// Server is an HTTP server.
type Server struct {
	engine   			*gin.Engine
	hs                  *http.Server
	tlsConf     		*tls.Config
	lis         		net.Listener
	network     string
	endpoint 			*url.URL

	engineHandle 		GinEngineHandle
	enableCrossDomain	bool			// 允许跨域
	enablePrometheus    bool            // enable prometheus
	// 限流器
	enableLimit         bool
	limiterLimit  		time.Duration
	limiterBurst		int


	once     sync.Once
	err      error
	address  string
	timeout  time.Duration

	// metrics
	// counter:
	promRequests *prometheus.CounterVec
	// histogram:
	promSeconds *prometheus.HistogramVec

	log      *log.Helper
}

func NewServer(address string, h GinEngineHandle, opts ...ServerOption) *Server {
	srv := &Server{
		network: 	"tcp",
		address: address,
		engineHandle: h,
		timeout: 1 * time.Second,
	}
	for _, o := range opts {
		o(srv)
	}
	if srv.log == nil {
		srv.log = log.NewHelper(log.With(log.DefaultLogger, "module", "transport/http"))
	}
	srv.err = srv.listenAndEndpoint()
	return srv
}

// Endpoint return a real address to registry endpoint.
// examples:
//   https://127.0.0.1:8000
//   Legacy: http://127.0.0.1:8000?isSecure=false
func (s *Server) Endpoint() (*url.URL, error) {
	if s.err != nil {
		return nil, s.err
	}
	return s.endpoint, nil
}

func (s *Server) Start(context.Context) error {
	if s.engine == nil {
		s.engine = gin.Default()
	}
	if s.enableLimit {
		// 限流器
		limiter := rate.NewLimiter(rate.Every(s.limiterLimit), s.limiterBurst)
		s.engine.Use(func(ctx *gin.Context) {
			if !limiter.Allow() {
				ctx.AbortWithStatus(http.StatusTooManyRequests)
				return
			}
			ctx.Next()
		})
	}
	if s.enablePrometheus {
		// prometheus
		s.engine.GET("/metrics", gin.WrapH(promhttp.Handler()))
	}
	if s.enableCrossDomain {
		// 允许跨域
		s.engine.Use(func(c *gin.Context) {
			method := c.Request.Method
			c.Header("Access-Control-Allow-Origin", "*")
			c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
			c.Header("Access-Control-Allow-Credentials", "true")

			//放行所有OPTIONS方法
			if method == "OPTIONS" {
				c.AbortWithStatus(http.StatusNoContent)
			}
			// 处理请求
			c.Next()
		})
	}

	s.engine.Use(func(c *gin.Context) {
		start := time.Now()

		var tc *trace.Trace
		{
			body, err := c.GetRawData()
			if err != nil {
				s.log.Error(err)
			}

			tc = trace.New("")
			decodedURL, _ := url.QueryUnescape(c.Request.URL.RequestURI())
			tc.Request = &trace.Request{
				Method:     c.Request.Method,
				DecodedURL: decodedURL,
				Header:     c.Request.Header,
				Body:       body,
			}
			c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) // re-construct req body


			c.Set(keyTrace, tc)
		}
		method := c.Request.Method


		defer func() {
			if err := recover(); err != nil {
				stackInfo := string(debug.Stack())
				s.log.Errorf("got panic panic:%+v, stack:%v", err, stackInfo)
			}

			path := c.Request.URL.Path
			var ackBody string
			if em, ok := c.Get(CtxResponseBodyKey); ok {
				if em != nil {
					dd, e := json.Marshal(em)
					if e == nil {
						ackBody = string(dd)
					}
				}
			}
			tc.Response = &trace.Response{
				Header:          c.Writer.Header(),
				Body:            ackBody,
				BusinessCode:    0,
				BusinessCodeMsg: "",
				HttpCode:        c.Writer.Status(),
				HttpCodeMsg:     http.StatusText(c.Writer.Status()),
				CostSeconds:     0,
			}

			since := time.Since(start).Seconds()
			statusCode := c.Writer.Status()
			clientIP := c.ClientIP()
			var errMsg string
			if em, ok := c.Get(CtxErrorKey); ok {
				errMsg = em.(*errors.Error).Error()
			}
			isSuccess := statusCode == http.StatusOK
			level := log.LevelInfo
			if !isSuccess {
				level = log.LevelWarn
			}
			if isSuccess {
				s.log.Log(level, log.DefaultMessageKey,     "REQUEST-SUCCESS",
					"since",      fmt.Sprintf("%fs", since), // time to process
					"clientIp",   clientIP,
					"trace_info", tc)
			} else {
				s.log.Log(level, log.DefaultMessageKey,     "REQUEST-ERROR",
					"errMsg",     errMsg,
					"since",      fmt.Sprintf("%fs", since), // time to process
					"clientIp",   clientIP,
					"trace_info", tc)
			}

			if s.promRequests != nil {
				s.promRequests.WithLabelValues(method, path).Inc()
			}
			if s.promSeconds != nil {
				s.promSeconds.WithLabelValues(method, path, strconv.Itoa(statusCode), strconv.Itoa(0)).Observe(since)
			}
		} ()
		c.Next()
	})

	if s.engineHandle != nil {
		s.engineHandle(s.engine)
	}
	s.log.Infof("[http] server listening on: %s", s.address)

	s.hs = &http.Server{Addr: s.address, Handler: s.engine}
	if s.tlsConf != nil {
		return s.hs.ServeTLS(s.lis, "", "")
	} else {
		return s.hs.Serve(s.lis)
	}
	//return s.hs.ListenAndServe()
	//return s.engine.Run(s.address)
}
func (s *Server) Stop(ctx context.Context) error {
	s.hs.Shutdown(ctx)
	s.log.Info("[http] server stopping")
	return nil
}

func (s *Server) listenAndEndpoint() error {
	if s.lis == nil {
		lis, err := net.Listen("tcp", s.address)
		if err != nil {
			return err
		}
		s.lis = lis
	}
	addr, err := host.Extract(s.address, s.lis)
	if err != nil {
		_ = s.lis.Close()
		return err
	}
	s.log.Debugf("host.Extract addr=%s", addr)
	s.endpoint = endpoint.NewEndpoint(endpoint.Scheme("http", s.tlsConf != nil), addr)
	return nil
}